#include <sys/select.h>
#include "wrap.h"

/**
 * 多路 IO 转接（多路IO服用）：内核监听多个文件描述符的属性（读写缓冲区）变化
 * 如果某个文件描述符的读缓冲区变化了，就可以进行读操作为了。会将这个事件告诉应用层。
 *      select      Windows平台使用select，跨平台
 *      epoll       Linux
 *      poll        很少使用
 * @return
 */
//int main(int argc,char * argv[]) {return 1;}

#define PORT 8080


/**
 * select
 *      优点
 *          跨平台
 *      缺点
 *          文件描述符的限制 1024 由于FD_SETSIZE的限制
 *          只是返回变化的文件描述符的个数，具体的变化需要遍历
 *          每次都需要将需要监听的文件描述符给到内核
 *          大量并发，少量活跃，select效率低
 *      可以使用 dup2 对某个文件描述符进行重定向。然后关闭某个文件描述符
 * @param argc
 * @param argv
 * @return
 */
int main(int argc,char * argv[]) {

    fd_set oldset,newset;
    // 创建套接字
    int socket_fd = tcp4bind(PORT,NULL);
    // 监听
    Listen(socket_fd,128);
    // 最大的文件描述符
    int maxfd = socket_fd;
    FD_ZERO(&oldset);
    FD_ZERO(&newset);
    FD_SET(socket_fd,&oldset);
    while (1){
        newset = oldset;
        int n = select(maxfd + 1, &newset,NULL,NULL,NULL);
        if(n < 0){
            perror("select");
            break;
        }
        // 文件描述符没有变化
        else if(n == 0){
            continue;
        }

        // 能走到这里就是监听到了文件描述符的变化
        // socket_fd 变化
        if(FD_ISSET(socket_fd,&newset)){
            struct sockaddr_in client_addr;
            socklen_t len = sizeof (client_addr);
            char ip[16] = "";
            //提取新的链接
            int client_fd = Accept(socket_fd,(struct sockaddr *)&client_addr ,&len);
            printf("client connect ==> ip= %s port= %d\n", inet_ntop(AF_INET,&client_addr.sin_addr,ip,16), ntohs(client_addr.sin_port));
            //将 client_fd 加入到 oldset 中 方便下次的监听
            FD_SET(client_fd,&oldset);
            // 更新最大的文件描述符
            if(maxfd < client_fd){
                maxfd = client_fd;
            }
            if(--n == 0){
                continue;
            }
        }
        int newMaxfd = maxfd;
        printf("maxfd = %d\n",maxfd );
        // 提取后的文件描述符变化 遍历socket_fd之后的文件描述
        for (int i = socket_fd + 1; i <= maxfd; ++i) {
            if(i > newMaxfd){
                break;
            }
            // 如果在集合里面
            again: if(FD_ISSET(i,&newset)){
                char buff[1500] = "";
                int ret = Read(i,buff,sizeof(buff));
                // 出错 将当前的文件描述符关闭，并且将该文件描述符从 oldset 中删除
                if(ret < 0){
                    perror("Read");
                    close(i);
                    // 如果当前链接为最后一个链接
                    if(i == newMaxfd){
                        FD_CLR(i,&oldset);
                        newMaxfd --;
                        continue;
                    }
                    // 如果当前链接不是最后一个链接，则将当前文件描述符重新指向 末尾的文件描述符
                    int rr = dup2(newMaxfd,i);
                    if(rr == -1){
                        perror("dup2");
                        FD_CLR(i,&oldset);
                        continue;
                    }
                    // 关闭末尾的文件描述符
                    close(newMaxfd);
                    FD_CLR(newMaxfd,&oldset);
                    // 最大文件描述符 减小
                    newMaxfd --;
                    // 此时 i 代表原来的最大文件描述符，自然需要重新进行操作
                    goto again;
                    continue;
                }
                else if(ret == 0){
                    close(i);
                    if(i == newMaxfd){
                        FD_CLR(i,&oldset);
                        newMaxfd --;
                        continue;
                    }
                    int rr = dup2(newMaxfd,i);
                    if(rr == -1){
                        perror("dup2");
                        FD_CLR(i,&oldset);
                        continue;
                    }
                    close(newMaxfd);
                    FD_CLR(newMaxfd,&oldset);
                    newMaxfd --;
                    printf("exit success! \n");
                    goto again;
                    continue;
                }

                printf("%d ==> %s",i,buff);
                Write(i,buff,ret);
            }
        }
        //
        maxfd = newMaxfd;
    }
    return 1;
}

